package current.eating;

import java.util.concurrent.*;

public class DiningPhilosophersDeadlockBlockingQueue implements Runnable{

    Philosophy[] phis;
    // 叉子
    volatile int[] forks = new int[5];
    LinkedBlockingQueue<Philosophy> workingQueue;
    LinkedBlockingQueue<Philosophy> managerQueue;
    DelayQueue<DelayInterruptingThread> delayQueue = new DelayQueue<>();

    public DiningPhilosophersDeadlockBlockingQueue() {
        phis = new Philosophy[5];
        forks = new int[5];
        workingQueue = new LinkedBlockingQueue<>();
        managerQueue = new LinkedBlockingQueue<>();
        for(int i = 0; i < 5; i++) {
            phis[i] = new Philosophy(i+1);
            workingQueue.offer(phis[i]);
        }
    }

    static class DelayInterruptingThread implements Delayed {

        long time;
        Thread current;

        public DelayInterruptingThread(Thread t, long delay) {
            this.current = t;
            this.time = System.currentTimeMillis() + delay;
        }

        @Override
        public long getDelay(TimeUnit unit) {
            return time - System.currentTimeMillis();
        }

        @Override
        public int compareTo(Delayed o) {
            return (int) (time - ((DelayInterruptingThread)o).time);
        }

        public void rollback() {
            if(this.current != null) {
                this.current.interrupt();
            }
        }

        public void commit() {
            this.current = null;
        }
    }

    class InterruptingWorker implements Runnable {

        @Override
        public void run() {
            while (true) {
                try {
                    var delayed = (DelayInterruptingThread)delayQueue.take();
                    delayed.rollback();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    class ContentionManager implements Runnable {

        @Override
        public void run() {
            while(true) {
                try {
                    var phi = managerQueue.take();
                    if(phi.checkLeft(forks) && phi.checkRight(forks)) {
                        phi.takeLeft(forks);
                        phi.takeRight(forks);
                        workingQueue.offer(phi);
                    } else {
                        managerQueue.offer(phi);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    class Worker implements Runnable {
        @Override
        public void run() {

            while (true) {
                Philosophy phi = null;
                try{
                    phi = workingQueue.take();
                    if("Hungry".equals(phi.getState())) {
                        var delayItem = new DelayInterruptingThread(Thread.currentThread(), 1000);
                        // 如果eating失败了，通过延迟队列走到异常处
                        delayQueue.offer(delayItem);
                        phi.eating();
                        // eating成功就commit
                        delayItem.commit();
                        phi.putLeft(forks);
                        phi.putRight(forks);
                        phi.finish();
                        workingQueue.offer(phi);
                    } else {
                        phi.thinking();
                        managerQueue.offer(phi);
                    }
                } catch (InterruptedException e) {
                    if(phi != null) {
                        // 回滚操作
                        phi.putLeft(forks);
                        phi.putRight(forks);
                        if("Eating".equals(phi.getState())) {
                            phi.setState("Hungry");
                        }
                        managerQueue.offer(phi);
                    }
                }
            }
        }
    }

    public void run(){
        var pool = Executors.newFixedThreadPool(7);
        for(int i = 0; i < 5; i++) {
            pool.submit(new Worker());
        }
        pool.submit(new ContentionManager());
        pool.submit(new InterruptingWorker());
    }

    public static void main(String[] args) {
        var solver = new DiningPhilosophersDeadlockBlockingQueue();
        solver.run();
    }
}
